﻿using CommunityToolkit.Mvvm.Input;
using FFmpeg.NET;
using Notifications.Wpf;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows.Input;
using VideoTools.Tasks;
using VideoTools.Util;
using VideoTools.vo;
using TaskStatus = VideoTools.vo.TaskStatus;

namespace VideoTools.Commands
{
    public class ScaleVideoCommands : NotifyPropertyBase
    {
        private static volatile ScaleVideoCommands _instance;
        private static readonly object SyncRoot = new object();
        static Dictionary<string, ScaleVideoTaskItem> Task_Context =
            new Dictionary<string, ScaleVideoTaskItem>();
        static Dictionary<string, IProgress<double>> Progress_Context =
            new Dictionary<string, IProgress<double>>();

        private static readonly string Scale_Option = "压缩,1/4,1/2,2/3,1/3,3/4,1/5,1/6,360P,480P";
        private static readonly string Fps_Option = "10,15,20,24,25,30,60";
        public static ScaleVideoCommands Instance
        {
            get
            {
                if (_instance == null)
                {
                    lock (SyncRoot)
                    {
                        if (_instance == null)
                        {
                            _instance = new ScaleVideoCommands();
                        }
                    }
                }

                return _instance;
            }
        }

        private ScaleVideoCommands()
        {
            foreach (string so in Scale_Option.Split(",").ToList<string>())
            {
                _ScaleOptions.Add(so);
            }
            _FpsList.Add(AppConstants.FpsTips.ToString());
            foreach (string fo in Fps_Option.Split(",").ToList<string>())
            {
                _FpsList.Add(fo);
            }
            foreach (DeviceType dt in Utils.GetAllEnumValues<DeviceType>())
            {
                DeviceTypes.Add(dt);
            }
        }

        private bool _LanguageListEnable = true;

        public bool LanguageListEnable
        {
            get => _LanguageListEnable;
            set => SetProperty(ref _LanguageListEnable, value);
        }
        private bool _ClearListEnable = true;

        public bool ClearListEnable
        {
            get => _ClearListEnable;
            set => SetProperty(ref _ClearListEnable, value);
        }

        private bool _DeviceTypeEnable = true;

        public bool DeviceTypeEnable
        {
            get => _DeviceTypeEnable;
            set => SetProperty(ref _DeviceTypeEnable, value);
        }
        private bool _DoCommandEnable = true;

        public bool DoCommandEnable
        {
            get => _DoCommandEnable;
            set => SetProperty(ref _DoCommandEnable, value);
        }
        private bool _DeleteSelectedEnable = true;

        public bool DeleteSelectedEnable
        {
            get => _DeleteSelectedEnable;
            set => SetProperty(ref _DeleteSelectedEnable, value);
        }
        private bool _CopyCommandEnable = true;

        public bool CopyCommandEnable
        {
            get => _CopyCommandEnable;
            set => SetProperty(ref _CopyCommandEnable, value);
        }

        private ObservableCollection<DeviceType> _DeviceTypes =
            new ObservableCollection<DeviceType>();

        public ObservableCollection<DeviceType> DeviceTypes
        {
            get => _DeviceTypes;
            set => SetProperty(ref _DeviceTypes, value);
        }
        private ObservableCollection<string> _ScaleOptions = new ObservableCollection<string>();

        public ObservableCollection<string> ScaleOptions
        {
            get => _ScaleOptions;
        }

        private ObservableCollection<string> _FpsList = new ObservableCollection<string>();

        public ObservableCollection<string> FpsList
        {
            get => _FpsList;
        }

        private bool _ShutDownOnDoneAll;

        public bool ShutDownOnDoneAll
        {
            get => _ShutDownOnDoneAll;
            set => SetProperty(ref _ShutDownOnDoneAll, value);
        }
        private bool _FpsListEnable = true;

        public bool FpsListEnable
        {
            get => _FpsListEnable;
            set => SetProperty(ref _FpsListEnable, value);
        }
        private DeviceType DeviceType = DeviceType.GPU;
        private int fps;
        private (int fz, int fm) ScaleSize = (1, 1);
        private ICommand _DoCommand;
        private ICommand _OpenFileCommand;
        private ICommand _StopAllCommand;
        private ICommand _DeleteSelectedCommand;
        private ICommand _DeleteDoneCommand;
        private ICommand _ClearListCommand;
        private ICommand _ChangeDeviceTypeCommand;
        private ICommand _ChangeScaleCommand;
        private ICommand _FpsChangedCommand;
        private ICommand _CopyTaskLogCommand;
        private ICommand _CopyCommandCommand;

        public ICommand CopyCommandCommand =>
            _CopyCommandCommand
            ?? (
                _CopyCommandCommand = new RelayCommand<object>(o =>
                {
                    if (ScaleVideoTaskQueueManager.Instance.Tasks.Count == 0)
                    {
                        return;
                    }
                    StringBuilder sb = new StringBuilder();
                    foreach (ScaleVideoTaskItem item in ScaleVideoTaskQueueManager.Instance.Tasks)
                    {
                        string v = getParam(item);
                        sb.AppendLine($"ffmpeg {v}");
                    }
                    string text = sb.ToString();
                    if (string.IsNullOrEmpty(text))
                        return;
                    Utils.CopyToClipboard(text, "命令");
                })
            );
        public ICommand CopyTaskLogCommand =>
            _CopyTaskLogCommand
            ?? (
                _CopyTaskLogCommand = new RelayCommand<object>(o =>
                {
                    if (o == null)
                        return;
                    ScaleVideoTaskItem model = (ScaleVideoTaskItem)o;
                    Utils.CopyToClipboard(model.CopyLog(), "日志");
                })
            );

        public ICommand FpsChangedCommand =>
            _FpsChangedCommand
            ?? (
                _FpsChangedCommand = new RelayCommand<object>(o =>
                {
                    if (o == null)
                        return;

                    if (o is string info)
                    {
                        if (info.Equals(AppConstants.FpsTips))
                        {
                            fps = 0;
                        }
                        else
                        {
                            fps = int.Parse(info);
                        }
                    }
                })
            );
        public ICommand ChangeScaleCommand =>
            _ChangeScaleCommand
            ?? (
                _ChangeScaleCommand = new RelayCommand<object>(o =>
                {
                    if (o == null)
                        return;
                    string sc = (string)o;
                    if ("压缩".Equals(sc))
                    {
                        ScaleSize = (1, 1);
                    }
                    else if (sc.Contains("P"))
                    {
                        int ap = int.Parse(sc.Replace("P", ""));
                        ScaleSize = (ap, ap);
                    }
                    else
                    {
                        string[] infos = sc.Split("/");
                        ScaleSize = (int.Parse(infos[0]), int.Parse(infos[1]));
                    }
                })
            );
        public ICommand ChangeDeviceTypeCommand =>
            _ChangeDeviceTypeCommand
            ?? (
                _ChangeDeviceTypeCommand = new RelayCommand<object>(o =>
                {
                    if (o == null)
                        return;

                    DeviceType = (DeviceType)o;
                })
            );
        public ICommand DoCommand =>
            _DoCommand
            ?? (
                _DoCommand = new RelayCommand<object>(o =>
                {
                    if (o == null)
                    {
                        return;
                    }
                    if (
                        ScaleVideoTaskQueueManager
                            .Instance.Tasks.Where(t => t.Status != TaskStatus.Completed)
                            .Count() == 0
                    )
                    {
                        return;
                    }
                    ScaleVideoTaskQueueManager.Instance.StartProcessing();

                    ClearListEnable = false;
                    DeviceTypeEnable = false;
                    DoCommandEnable = false;
                    DeleteSelectedEnable = false;
                    CopyCommandEnable = false;
                })
            );
        public ICommand OpenFileCommand =>
            _OpenFileCommand
            ?? (
                _OpenFileCommand = new RelayCommand<object>(o =>
                {
                    System.Windows.Forms.OpenFileDialog openFileDialog =
                        new System.Windows.Forms.OpenFileDialog();
                    string filter = "视频文件(";
                    foreach (string allow in AppConstants.__allowVideoFiles)
                    {
                        filter += "*." + allow + ",";
                    }
                    filter = filter.Remove(filter.LastIndexOf(","));
                    filter += ")|";
                    foreach (string allow in AppConstants.__allowVideoFiles)
                    {
                        filter += "*." + allow + ";";
                    }
                    filter = filter.Remove(filter.LastIndexOf(";"));
                    openFileDialog.Filter = filter;
                    openFileDialog.Multiselect = true;
                    openFileDialog.RestoreDirectory = true;
                    openFileDialog.FilterIndex = 1;
                    string[] files = null;
                    if (openFileDialog.ShowDialog() == DialogResult.OK)
                    {
                        files = openFileDialog.FileNames;
                    }
                    else
                    {
                        return;
                    }
                    for (int i = 0; i < files.Length; i++)
                    {
                        string f = files[i];
                        ScaleVideoTaskItem taskItem = GetTaskItem(f);
                        if (taskItem == null)
                        {
                            continue;
                        }
                        int cnt = ScaleVideoTaskQueueManager.Instance.Tasks.Count;
                        ScaleVideoTaskQueueManager.Instance.AddTask(
                            taskItem,
                            TaskAction,
                            TaskActionBefore,
                            TaskActionAfter
                        );
                    }
                })
            );

        private ScaleVideoTaskItem GetTaskItem(string f)
        {
            Engine ffmpeg = new Engine(@"ffmpeg.exe");
            InputFile inputFile = new InputFile(f);
            CancellationTokenSource source = new CancellationTokenSource();
            MetaData data = ffmpeg
                .GetMetaDataAsync(inputFile, source.Token)
                .GetAwaiter()
                .GetResult();
            if (data == null)
            {
                throw new Exception("视频有问题1");
            }
            ScaleVideoTaskItem item = new ScaleVideoTaskItem() { Path = f };
            item.AudioChannel = data.AudioData.ChannelOutput;
            int seconds = (int)data.Duration.TotalSeconds;
            item.Duration = TimeSpan.FromSeconds(seconds);
            if (data.VideoData != null)
            {
                StringBuilder sb = new StringBuilder();
                Random random = new Random();
                TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
                for (int i = 0; i < 10; i++)
                {
                    int index = random.Next(AppConstants.Chars.Length);
                    sb.Append(AppConstants.Chars[index]);
                }
                string currentImageFile =
                    "./"
                    + AppConstants.ImageFileStart
                    + Convert.ToInt64(ts.TotalMilliseconds).ToString()
                    + "_"
                    + sb.ToString()
                    + ".jpg";
                var outputFile = new OutputFile(currentImageFile);
                int durationMilliseconds = (int)data.Duration.TotalMilliseconds;
                ConversionOptions conversionOptions = new ConversionOptions()
                {
                    Seek = TimeSpan.FromMilliseconds(random.Next(1, durationMilliseconds - 1)),
                };
                ffmpeg
                    .GetThumbnailAsync(inputFile, outputFile, conversionOptions, source.Token)
                    .GetAwaiter()
                    .GetResult();
                Image image = Image.FromFile(currentImageFile);
                item.FrameSize = image.Width + "x" + image.Height;
            }
            return item;
        }

        private Action<CancellationToken, IProgress<double>, ScaleVideoTaskItem> TaskAction = (
            token,
            progress,
            item
        ) =>
        {
            try
            {
                Process process = new Process();
                token.Register(() =>
                {
                    if (!process.HasExited)
                    {
                        item.Killed = true;
                        process.Kill();
                    }
                });
                process.StartInfo.FileName = "ffmpeg ";
                process.StartInfo.WindowStyle = ProcessWindowStyle.Normal;
                string param = item.Command;
                item.Info(AppConstants.DoubleClickMessage);
                item.Info("参数 ffmpeg " + param);
                process.StartInfo.Arguments = param;
                process.StartInfo.CreateNoWindow = true; //显示命令行窗口
                process.StartInfo.UseShellExecute = false;
                IDictionary<string, string?> environment = process.StartInfo.Environment;
                environment["taskItem_Path"] = item.Path;
                Task_Context.Add(item.Path, item);
                Progress_Context.Add(item.Path, progress);
                //将输出信息重定向
                process.StartInfo.RedirectStandardInput = true;
                process.StartInfo.RedirectStandardOutput = true;
                process.StartInfo.RedirectStandardError = true;
                process.EnableRaisingEvents = true;
                process.Exited += new EventHandler(scale_Exited);
                process.OutputDataReceived += new DataReceivedEventHandler(
                    scale_OutputDataReceived
                );
                process.ErrorDataReceived += new DataReceivedEventHandler(scale_ErrorDataReceived);
                process.Start();
                //开始异步读取输出
                process.BeginOutputReadLine();
                process.BeginErrorReadLine();
                process.WaitForExit();
            }
            catch
            {
                item.Status = TaskStatus.Failed;
            }
            finally { }
        };

        private Action<CancellationToken, ScaleVideoTaskItem> TaskActionBefore = (token, item) =>
        {
            item.Command = getParam(item);
        };

        static void scale_OutputDataReceived(Object sender, DataReceivedEventArgs e) { }

        static void scale_ErrorDataReceived(Object sender, DataReceivedEventArgs e)
        {
            Process process = (Process)sender;
            string path = process.StartInfo.Environment["taskItem_Path"];
            ScaleVideoTaskItem taskItem = Task_Context[path];
            IProgress<double> progress = Progress_Context[path];
            string info = e.Data;
            if (info == null)
            {
                return;
            }
            taskItem.Info(e.Data);
            foreach (string keyword in "frame,fps,size,time,bitrate,speed".Split(','))
            {
                if (!info.Contains(keyword))
                {
                    return;
                }
            }
            Match match = Regex.Match(info, "^frame.+time=(.+)bitrate=.+");
            if (match.Success)
            {
                string time = match.Groups[1].Value;
                if (time.Contains("."))
                {
                    time = time.Split(".")[0];
                }
                string[] times = time.Split(":");
                int cur =
                    int.Parse(times[0]) * 60 * 60 + int.Parse(times[1]) * 60 + int.Parse(times[2]);

                TimeSpan Duration = taskItem.Duration;
                int duration = (int)Duration.TotalSeconds;
                double rx = (Convert.ToDouble(cur) / Convert.ToDouble(duration));
                taskItem.Progress = rx;
                DateTime now = DateTime.Now;
                taskItem.SpendTime = new TimeSpan(now.Ticks - taskItem.startTime.Ticks).ToString(
                    @"hh\:mm\:ss"
                );
                Serilog.Log.Information("进度 " + taskItem.Progress);
            }
        }

        static void scale_Exited(Object sender, EventArgs e)
        {
            Process process = (Process)sender;
            string path = process.StartInfo.Environment["taskItem_Path"];
            ScaleVideoTaskItem taskItem = Task_Context[path];
            taskItem.Info("任务退出" + path);
            Task_Context.Remove(path);
            Progress_Context.Remove(path);
        }

        private static (string width, string height) GetNewSize(string frameSize)
        {
            (int fz, int fm) scaleSize = Instance.ScaleSize;
            if (scaleSize.fz == 0 || scaleSize.fm == 0)
            {
                return (null, null);
            }
            string[] frameInfo = frameSize.Split("x");
            int w = Int32.Parse(frameInfo[0]);
            int h = Int32.Parse(frameInfo[1]);
            bool usingHeight = w > h ? true : false;
            if (scaleSize.fz > 100)
            {
                return (
                    Convert.ToString(usingHeight ? w * scaleSize.fz / h : scaleSize.fz),
                    Convert.ToString(usingHeight ? scaleSize.fz : h * scaleSize.fz / w)
                );
            }
            else
            {
                return (
                    Convert.ToString(w * scaleSize.fz / scaleSize.fm),
                    Convert.ToString(h * scaleSize.fz / scaleSize.fm)
                );
            }
        }

        private static string getParam(ScaleVideoTaskItem item)
        {
            string param = "-i \"" + item.Path + "\"";
            if (Instance.fps != 0)
            {
                param += " -r " + Instance.fps;
            }
            var size = GetNewSize(item.FrameSize);
            if (size.width == null || size.height == null)
            {
                throw new Exception("视频有问题");
            }
            string path = System.IO.Path.GetDirectoryName(item.Path);
            FileInfo f = new FileInfo(item.Path);
            string tempName = f.Name.Remove(f.Name.LastIndexOf("."));
            string outFile = path + "\\" + tempName + AppConstants.Scale + ".mp4";
            param += " -s " + size.width + "x" + size.height;
            param += " -ac 2 ";
            param += " \"" + outFile + "\" -y";

            if (Instance.DeviceType == DeviceType.GPU)
            {
                param = "-i \"" + item.Path + "\"";
                if (Instance.fps != 0)
                {
                    param += " -r " + Instance.fps;
                }
                param +=
                    " -s "
                    + size.width
                    + "x"
                    + size.height
                    + " -c:v:0 hevc_nvenc -b_ref_mode disabled -cq:v 24 -c:a aac -strict -2 -rtbufsize 30m -max_muxing_queue_size 1024 ";
                param += " \"" + outFile + "\" -y";
            }
            item.OutPath = outFile;
            return param;
        }

        private Action<CancellationToken, ScaleVideoTaskItem> TaskActionAfter = (token, item) =>
        {
            var contentb = new NotificationContent()
            {
                Title = "视频分辨率调整成功,点击打开",
                Type = NotificationType.Information,
                Message = item.Path,
            };
            AppManager.Instance.NotificationManager.Show(
                contentb,
                onClick: () => Process.Start(AppConfig.Instance.VideoPlayerPath, item.OutPath)
            );
        };

        public ICommand StopAllCommand =>
            _StopAllCommand
            ?? (
                _StopAllCommand = new RelayCommand<object>(o =>
                {
                    ScaleVideoTaskQueueManager.Instance.StopAllTasks();
                    ClearListEnable = true;
                    DeviceTypeEnable = true;
                    DoCommandEnable = true;
                    DeleteSelectedEnable = true;
                    Instance.CopyCommandEnable = true;
                })
            );

        public ICommand DeleteSelectedCommand =>
            _DeleteSelectedCommand
            ?? (
                _DeleteSelectedCommand = new RelayCommand<object>(o =>
                {
                    ScaleVideoTaskQueueManager.Instance.DeleteSelected();
                })
            );
        public ICommand ClearListCommand =>
            _ClearListCommand
            ?? (
                _ClearListCommand = new RelayCommand<object>(o =>
                {
                    ScaleVideoTaskQueueManager.Instance.ClearTasks();
                })
            );

        public ICommand DeleteDoneCommand =>
            _DeleteDoneCommand
            ?? (
                _DeleteDoneCommand = new RelayCommand<object>(o =>
                {
                    ScaleVideoTaskQueueManager.Instance.ClearCompletedTasks();
                })
            );
    }
}
